/*
 * Copyright (c) 2015-2016, NVIDIA CORPORATION.  All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms and conditions of the GNU General Public License,
 * version 2, as published by the Free Software Foundation.
 *
 * This program is distributed in the hope it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 * more details.
 */

#include <asm/traps.h>
#include <linux/clk.h>
#include <linux/debugfs.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/device.h>
#include <linux/platform/tegra/bridge_mca.h>
#include <linux/io.h>
#include <linux/ioport.h>
#include <soc/tegra/chip-id.h>

#define BUS_ADDR_MASK 0x3fffffff
#define BUS_ERROR_TYPE_MASK 0x3e0
#define BUS_ERROR_TYPE_SHIFT 5

static struct bridge_mca_error t18x_axi_errors[] = {
	{.desc = "Reserved"}, /* 0 */
	{.desc = "Illegal TZ G0 security access"},
	{.desc = "Illegal TZ G1 security access"},
	{.desc = "Illegal TZ G2 security access"},
	{.desc = "Illegal TZ G3 security access"},
	{.desc = "Illegal TZ G4 security access"}, /* 5 */
	{.desc = "Illegal TZ G5 security access"},
	{.desc = "Illegal TZ G6 security access"},
	{.desc = "Illegal TZ G7 security access"},
	{.desc = "Illegal priviledged access"},
	{.desc = "Address decode error generated by bridge"}, /* 10 */
	{.desc = "Insecure access to a slave"},
	{.desc = "Insecure master ID access"},
	{.desc = "Unsupported FIXED access type"},
	{.desc = "Unsupported WRAP access type"},
	{.desc = "Unsupported INCR access type"}, /* 15 */
	{.desc = "Unsupported burst size error"},
	{.desc = "Unsupported alignment type error"},
	{.desc = "Timeout error"},
	{.desc = "Illegal BE access error"},
	{.desc = "Slave error"}, /* 20 */
	{.desc = "Slave decode error"},
	{.desc = "Bridge error"},
};

static char *src_ids[] = {
	"Reserved",		/* 0000 */
	"CCPLEX",		/* 0001 */
	"CCPLEX-DPMU",		/* 0010 */
	"BPMP",			/* 0011 */
	"SPE",			/* 0100 */
	"CPE/SCE",		/* 0101 */
	"DMA_PER",		/* 0110 */
	"TSECA",		/* 0111 */
	"TSECB",		/* 1000 */
	"JTAGM",		/* 1001 */
	"CSITE",		/* 1010 */
	"APE",			/* 1011 */
	"Reserved",		/* 1100 */
	"Reserved",		/* 1101 */
	"Reserved",		/* 1110 */
	"Bridge",		/* 1111 */
};

static char *dma_modes[] = {
	"APB Read/MC Write",
	"Memory to Memory Copy",
	"MC Read/APB Write",
	"Fixed Pattern"
};

static LIST_HEAD(bridge_list);
static DEFINE_RAW_SPINLOCK(bridge_lock);

static void print_mca(struct seq_file *file, const char *fmt, ...)
{
	va_list args;
	struct va_format vaf;

	va_start(args, fmt);

	if (file) {
		seq_vprintf(file, fmt, args);
	} else {
		vaf.fmt = fmt;
		vaf.va = &args;
		pr_crit("%pV", &vaf);
	}

	va_end(args);
}

static void print_cache(struct seq_file *file, u32 cache)
{
	if ((cache & 0x3) == 0x0) {
		print_mca(file, "\tCache: 0x%x -- Strongly Ordered\n", cache);
		return;
	}
	if ((cache & 0x3) == 0x1) {
		print_mca(file, "\tCache: 0x%x -- Device\n", cache);
		return;
	}

	switch (cache) {
	case 0x2:
		print_mca(file,
			  "\tCache: 0x%x -- Non-cacheable/Non-Bufferable\n",
			  cache);
		break;
	case 0x3:
		print_mca(file,
			  "\tCache: 0x%x -- Non-cacheable/Bufferable\n",
			  cache);
		break;
	default:
		print_mca(file, "\tCache: 0x%x -- Cacheable\n", cache);
	}
}

static void print_prot(struct seq_file *file, u32 prot)
{
	char *data_str;
	char *secure_str;
	char *priv_str;

	data_str = (prot & 0x4) ? "Instruction" : "Data";
	secure_str = (prot & 0x2) ? "Non-Secure" : "Secure";
	priv_str = (prot & 0x1) ? "Privileged" : "Unprivileged";

	print_mca(file, "\tProtection: 0x%x -- %s, %s, %s Access\n",
		  prot, priv_str, secure_str, data_str);
}

static void print_axi_id(struct seq_file *file, u32 src_id, u32 axi_id)
{
	char *cp;

	switch (src_id) {
	case BRIDGE_SRCID_CCPLEX:
	case BRIDGE_SRCID_CCPLEX_DPMU:
		if (axi_id & 0x40) {
			print_mca(file, "\tAXI_ID: 0x%x -- DPMU\n", axi_id);
		} else {
			if (axi_id < 0x04)
				print_mca(file,
					  "\tAXI_ID: 0x%x -- Denver Core %d\n",
					  axi_id, axi_id);
			else if (axi_id < 0x08)
				print_mca(file,
					  "\tAXI_ID: 0x%x -- A57 Core %d\n",
					  axi_id, axi_id - 0x4);
			else
				print_mca(file,
					  "\tAXI_ID: 0x%x -- Pool B\n", axi_id);
		}
		break;

	case BRIDGE_SRCID_BPMP:
		switch ((axi_id >> 3) & 0x7) {
		case 0x0:
			cp = "DMAD2NIC";
			break;
		case 0x1:
			cp = "CPUD2NIC";
			break;
		case 0x2:
			cp = "CPU2NIC";
			break;
		case 0x6:
			cp = "CVC2NIC";
			break;
		default:
			cp = "Unknown";
			break;
		}

		print_mca(file,
			  "\tAXI_ID: 0x%x -- Submaster ID: %s, ID: %d\n",
			  axi_id, cp, axi_id & 0x7);
		break;

	case BRIDGE_SRCID_CPE_SCE:
	case BRIDGE_SRCID_SPE:
		switch ((axi_id >> 4) & 0x3) {
		case 0x0:
			cp = "DMAD2NIC";
			break;
		case 0x2:
			cp = "CPUD2NIC";
			break;
		default:
			cp = "Unknown";
			break;
		}

		print_mca(file,
			  "\tAXI_ID: 0x%x -- Submaster ID: %s, ID: %d\n",
			  axi_id, cp, axi_id & 0xf);
		break;

	case BRIDGE_SRCID_APE:
		print_mca(file,
			  "\tAXI_ID: 0x%x -- %s Access, ID: 0x%x\n",
			  axi_id,
			  (axi_id & 0x40) ? "ADMA" : "ADSP",
			  axi_id & 0x3f);
		break;

	case BRIDGE_SRCID_DMA_PER:
		cp = dma_modes[(axi_id >> 5) & 0x3];
		print_mca(file,
			  "\tAXI_ID: 0x%x -- DMA Mode = %s, Channel ID = 0x%x\n",
			  axi_id, cp, axi_id & 0x1f);
		break;

	case BRIDGE_SRCID_CSITE:
		if (axi_id & 0x1) {
			print_mca(file, "\tAXI_ID: 0x%x -- CSITE\n", axi_id);
		} else {
			switch (axi_id) {
			case 0x00:
				print_mca(file, "\tAXI_ID: 0x%x -- MC Port 0\n",
					  axi_id);
				break;
			case 0x02:
				print_mca(file, "\tAXI_ID: 0x%x -- MC Port 1\n",
					  axi_id);
				break;
			default:
				print_mca(file, "\tAXI_ID: 0x%x\n", axi_id);
			}
		}
		break;

	default:
		print_mca(file, "\tAXI_ID: 0x%x\n", axi_id);
	}

}

static void print_bank_info(struct seq_file *file, struct bridge_mca_bank *bank)
{
	int count = 0;
	u32 bus_addr;
	u32 bus_status;
	u32 error_type;
	u32 id;
	u32 len;
	u32 prot;
	u32 axi_id;
	u32 src_id;
	u32 cache;
	u32 burst;
	u64 addr;
	struct resource *res = NULL;

	while (bank->error_fifo_count(bank->vaddr)) {
		bus_status = bank->error_status(bank->vaddr);
		bus_addr = bank->error_status(bank->vaddr);
		if (((bus_status >> 16) & 0xffff) == 0xdead &&
		    ((bus_addr >> 16) & 0xffff) == 0xdead)
			break;

		if (count > 0)
			print_mca(file,
				  "======================================\n");

		addr = get_bridge_addr(bus_addr);
		burst = get_bridge_burst(bus_addr);

		id = get_bridge_id(bus_status);
		error_type = get_bridge_err_type(bus_status);
		len = get_bridge_len(bus_status);
		prot = get_bridge_prot(bus_status);
		axi_id = get_bridge_axi_id(bus_status);
		src_id = get_bridge_src_id(bus_status);
		cache = get_bridge_cache(bus_status);

		print_mca(file, "Raw FIFO Entry: %d\n", count);
		print_mca(file, "\tADDR: 0x%x\n", bus_addr);
		print_mca(file, "\tSTAT: 0x%x\n", bus_status);
		print_mca(file, "--------------------------------------\n");

		print_mca(file, "Decoded FIFO Entry: %d\n", count);
		print_mca(file, "\tDirection: %s\n",
			  bus_status & BRIDGE_RW ? "READ" : "WRITE");
		print_mca(file, "\tBridge ID: 0x%x\n", id);
		print_mca(file, "\tError Type: 0x%x -- %s\n", error_type,
			  (error_type >= bank->max_error ? "Unknown" :
			   bank->errors[error_type].desc));
		print_mca(file, "\tLength: %d\n", len);
		print_prot(file, prot);

		print_mca(file, "\tSource ID: 0x%x -- %s\n",
			  src_id, src_ids[src_id]);
		print_axi_id(file, src_id, axi_id);
		print_cache(file, cache);
		print_mca(file, "\tBurst: 0x%x\n", burst);

		res = locate_resource(&iomem_resource, addr);
		if (res == NULL)
			print_mca(file, "\tAddress: 0x%llx (Unknown Device)\n",
				  addr);
		else
			print_mca(file, "\tAddress: 0x%llx -- %s + 0x%llx\n",
				  addr, res->name, addr - res->start);

		count += 1;
	}
}

static void print_bank(struct bridge_mca_bank *bank)
{
	if (bank->error_fifo_count(bank->vaddr) == 0)
		return;

	pr_crit("**************************************\n");
	pr_crit("CPU%d Machine check error in %s@0x%llx:\n",
		smp_processor_id(), bank->name, bank->bank);
	print_bank_info(NULL, bank);
	pr_crit("**************************************\n");
}

static int bridge_serr_hook(struct pt_regs *regs, int reason,
			unsigned int esr, void *priv)
{
	struct bridge_mca_bank *bank = priv;
	int retval = 1;

	if (!bank->seen_error &&
	    bank->error_fifo_count(bank->vaddr)) {
		print_bank(bank);
		bank->seen_error = 1;
		retval = 0;
	}
	return retval;
}

#ifdef CONFIG_DEBUG_FS
static DEFINE_MUTEX(bridge_mca_mutex);
static struct dentry *bridge_timeout_dir;
static struct dentry *bridge_timeout_node;
static int created_root = false;

static int bridge_mca_show(struct seq_file *file, void *data)
{
	struct bridge_mca_bank *bank;

	mutex_lock(&bridge_mca_mutex);

	list_for_each_entry(bank, &bridge_list, node) {
		print_mca(file, "Bridge %s@0x%llx:\n",
			  bank->name, bank->bank);

		if (bank->error_fifo_count(bank->vaddr) == 0x0) {
			print_mca(file, "\tNo Errors\n");
			continue;
		}

		print_bank_info(file, bank);
	}

	mutex_unlock(&bridge_mca_mutex);
	return 0;
}

static int bridge_mca_open(struct inode *inode, struct file *file)
{
	return single_open(file, bridge_mca_show, inode->i_private);
}

static const struct file_operations bridge_mca_fops = {
	.open = bridge_mca_open,
	.read = seq_read,
	.llseek = seq_lseek,
	.release = single_release
};

static int bridge_timeout_show(struct seq_file *s, void *unused)
{
	struct device *dev = s->private;
	struct resource *res_base;
	struct bridge_mca_bank *bank;
	unsigned long flags;
	struct platform_device *pdev;

	if (WARN_ON(!dev))
		return -EINVAL;

	pdev = to_platform_device(dev);

	res_base = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	if (!res_base) {
		dev_err(&pdev->dev, "Could not find base address");
		return -ENOENT;
	}

	raw_spin_lock_irqsave(&bridge_lock, flags);
	list_for_each_entry(bank, &bridge_list, node) {
		if (bank->bank == res_base->start) {
			seq_printf(s, "timeout = %u\n", bank->timeout);
			break;
		}
	}
	raw_spin_unlock_irqrestore(&bridge_lock, flags);
	return 0;
}

static ssize_t bridge_timeout_set(struct file *file,
	const char __user *addr, size_t len, loff_t *pos)
{
	struct seq_file *m = file->private_data;
	struct device *dev = m ? m->private : NULL;
	struct resource *res_base;
	struct bridge_mca_bank *bank;
	unsigned long flags;
	struct platform_device *pdev;
	u32 timeout;
	int ret;

	if (WARN_ON(!dev))
		return -EINVAL;

	pdev = to_platform_device(dev);

	ret = kstrtouint_from_user(addr, len, 10, &timeout);
	if (ret < 0)
		return ret;

	if (timeout > 100000) {
		dev_err(&pdev->dev, "bad value of timeout = %u\n",
				 timeout);
		return -EINVAL;
	}

	res_base = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	if (!res_base) {
		dev_err(&pdev->dev, "Could not find base address");
		return -ENOENT;
	}

	raw_spin_lock_irqsave(&bridge_lock, flags);
	list_for_each_entry(bank, &bridge_list, node) {
		if (bank->bank == res_base->start)
			break;
	}
	raw_spin_unlock_irqrestore(&bridge_lock, flags);

	bank->setup_timeout(bank->vaddr, timeout, &pdev->dev);
	bank->timeout = timeout;

	return len;
}

static int bridge_timeout_open(struct inode *inode, struct file *file)
{
	return single_open(file, bridge_timeout_show, inode->i_private);
}

static const struct file_operations timeout_fops = {
	.open = bridge_timeout_open,
	.read = seq_read,
	.write = bridge_timeout_set,
	.llseek = seq_lseek,
	.release = single_release
};

static int bridge_timeout_dbgfs_init(struct bridge_mca_bank *bank,
		struct device *dev)
{
	char devname[50];

	snprintf(devname, sizeof(devname), "%s@0x%llx", bank->name, bank->bank);

	bridge_timeout_dir = debugfs_create_dir(devname, NULL);
	if  (!bridge_timeout_dir) {
		dev_err(dev, "Failed to create bridge timeout debugfs dir");
		return -ENODEV;
	}

	bridge_timeout_node = debugfs_create_file("timeout", S_IRUGO,
				bridge_timeout_dir, dev, &timeout_fops);
	if (!bridge_timeout_node) {
		dev_err(dev, "Failed to create brige_timeout_node");
		return -ENODEV;
	}

	return 0;

}

static int bridge_mca_dbgfs_init(void)
{
	struct dentry *d;
	if (!created_root) {
		d = debugfs_create_file("tegra_bridge_mca",
				S_IRUGO, NULL, NULL, &bridge_mca_fops);
		if (IS_ERR_OR_NULL(d)) {
			pr_err("%s: could not create 'tegra_bridge_mca' node\n",
			       __func__);
			return PTR_ERR(d);
		}
		created_root = true;
	}
	return 0;
}
#else
static int bridge_mca_dbgfs_init(void) { return 0; }
static int bridge_timeout_dbgfs_init(struct bridge_mca_bank *bank,
		struct device *dev) { return 0; }
#endif

#define AXIAPB_TIMEOUT_TIMER	0x2c8
#define AXI2APB_ERROR_STATUS	0x2ec
#define AXI2APB_FIFO_STATUS3	0x2f8
#define AXI2APB_FIFO_ERROR_SHIFT	13
#define AXI2APB_FIFO_ERROR_MASK		0x1f

static unsigned int axi2apb_error_status(void __iomem *addr)
{
	unsigned int error_status;

	error_status = readl(addr+AXI2APB_ERROR_STATUS);

	writel(0xFFFFFFFF, addr+AXI2APB_ERROR_STATUS);
	return error_status;
}

static unsigned int axi2apb_error_fifo_count(void __iomem *addr)
{
	unsigned int fifo_status;

	fifo_status = readl(addr+AXI2APB_FIFO_STATUS3);

	fifo_status >>= AXI2APB_FIFO_ERROR_SHIFT;
	fifo_status &= AXI2APB_FIFO_ERROR_MASK;
	return fifo_status;
}

static unsigned int axi2apb_setup_timeout(void __iomem *addr, u32 timeout,
			struct device *dev)
{
	unsigned long rate;
	struct clk *clk;
	u32 value = 0;

	clk = devm_clk_get(dev, "axi_cbb");
	if (IS_ERR(clk)) {
		dev_info(dev, "can not get axi_cbb clock\n");
		return PTR_ERR(clk);
	}
	rate = clk_get_rate(clk);
	/*get rate in MHz*/
	rate = rate / 1000000;
	dev_info(dev, "axi_cbb clk rate = %lu MHZ, timeout = %u useconds\n",
		 rate, timeout);

	/*get timeout val for programming timer reg*/
	if (rate < 0x1000 && timeout < 0x100000 && rate > 0 && timeout > 0) {
		value = (u32)(rate * timeout);
		/* set timeout. By default, timeout config is enabled*/
		writel(value, addr + AXIAPB_TIMEOUT_TIMER);
		dev_info(dev, "enabled timeout = %u\n", value);
	} else
		dev_err(dev, "error enabling timeout = %u: rate=%lu, timeout=%u\n",
		 value, rate, timeout);

	return 0;
}

static struct tegra_bridge_data axi2apb_data = {
	.name = "AXI2APB",
	.error_status = axi2apb_error_status,
	.error_fifo_count = axi2apb_error_fifo_count,
	.setup_timeout = axi2apb_setup_timeout,
	.errors = t18x_axi_errors,
	.max_error = ARRAY_SIZE(t18x_axi_errors),
	.timeout = 97000			/* Default value of timer */
};


#define AXIP2P_SLV_RD_RESP_TIMER 0xf8
#define AXIP2P_SLV_WR_RESP_TIMER 0xfc
#define AXIP2P_TIMEOUT_CONFIG	0x104
#define AXIP2P_TIMEOUT_CONFIG_EN 0x00000001
#define AXIP2P_ERROR_STATUS	0x11c
#define AXIP2P_FIFO_STATUS	0x120
#define AXIP2P_FIFO_ERROR_SHIFT 26
#define AXIP2P_FIFO_ERROR_MASK  0x3f

static unsigned int axip2p_error_status(void __iomem *addr)
{
	unsigned int error_status;

	error_status = readl(addr+AXIP2P_ERROR_STATUS);

	writel(0xFFFFFFFF, addr+AXIP2P_ERROR_STATUS);
	return error_status;
}

static unsigned int axip2p_error_fifo_count(void __iomem *addr)
{
	unsigned int fifo_status;

	fifo_status = readl(addr+AXIP2P_FIFO_STATUS);

	fifo_status >>= AXIP2P_FIFO_ERROR_SHIFT;
	fifo_status &= AXIP2P_FIFO_ERROR_MASK;
	return fifo_status;
}

static unsigned int axip2p_setup_timeout(void __iomem *addr, u32 timeout,
			struct device *dev)
{
	unsigned long rate;
	struct clk *clk;
	u32 value = 0, tc_value = 0;

	clk = devm_clk_get(dev, "axi_cbb");
	if (IS_ERR(clk)) {
		dev_info(dev, "can not get axi_cbb clock\n");
		return PTR_ERR(clk);
	}
	rate = clk_get_rate(clk);
	/*get rate in MHz*/
	rate = rate / 1000000;
	dev_info(dev, "axi_cbb clk rate = %lu MHZ, timeout = %u useconds\n",
		 rate, timeout);

	/*get timeout val for programming timer reg*/
	if (rate < 0x1000 && timeout < 0x100000 && rate > 0 && timeout > 0) {
		value = (u32)(rate * timeout);
		/*Program timeout*/
		writel(value, addr + AXIP2P_SLV_RD_RESP_TIMER);
		writel(value, addr + AXIP2P_SLV_WR_RESP_TIMER);
		/* Enable timeout */
		tc_value = readl(addr + AXIP2P_TIMEOUT_CONFIG);
		writel(tc_value | AXIP2P_TIMEOUT_CONFIG_EN, addr +
			 AXIP2P_TIMEOUT_CONFIG);
		dev_info(dev, "enabled timeout = %u\n", value);
	} else
		dev_err(dev, "error enabling timeout = %u: rate=%lu, timeout=%u\n",
			value, rate, timeout);

	return 0;
}

static struct tegra_bridge_data axip2p_data = {
	.name = "AXIP2P",
	.error_status = axip2p_error_status,
	.error_fifo_count = axip2p_error_fifo_count,
	.setup_timeout = axip2p_setup_timeout,
	.errors = t18x_axi_errors,
	.max_error = ARRAY_SIZE(t18x_axi_errors),
	.timeout = 97000			/* Default value of timer */
};

static struct of_device_id tegra18_bridge_match[] = {
	{.compatible	= "nvidia,tegra186-AXI2APB-bridge",
	 .data		= &axi2apb_data},
	{.compatible	= "nvidia,tegra186-AXIP2P-bridge",
	 .data		= &axip2p_data},
	{},
};

MODULE_DEVICE_TABLE(of, tegra18_bridge_match);

static int tegra18_bridge_probe(struct platform_device *pdev)
{
	struct resource *res_base;
	struct bridge_mca_bank *bank;
	const struct tegra_bridge_data *bdata;
	struct serr_hook *hook;
	const struct of_device_id *match;
	unsigned long flags;
	int rc;
	u32 timeout;

	/*
	 * Bridges don't exist on the simulator
	 */
	if (tegra_cpu_is_asim())
		return 0;

	match = of_match_device(of_match_ptr(tegra18_bridge_match),
				&pdev->dev);

	if (!match) {
		dev_err(&pdev->dev, "No device match found");
		return -ENODEV;
	}

	bdata = match->data;

	res_base = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	if (!res_base) {
		dev_err(&pdev->dev, "Could not find base address");
		return -ENOENT;
	}

	rc = bridge_mca_dbgfs_init();
	if (rc)
		return rc;

	bank = devm_kzalloc(&pdev->dev, sizeof(*bank), GFP_KERNEL);
	bank->bank = res_base->start;
	bank->vaddr = devm_ioremap_resource(&pdev->dev, res_base);
	if(IS_ERR(bank->vaddr))
		return -EPERM;

	bank->name = bdata->name;
	bank->error_status = bdata->error_status;
	bank->error_fifo_count = bdata->error_fifo_count;
	bank->errors = bdata->errors;
	bank->max_error = bdata->max_error;
	bank->setup_timeout = bdata->setup_timeout;
	bank->seen_error = 0;
	bank->timeout = bdata->timeout;
	rc = bridge_timeout_dbgfs_init(bank, &pdev->dev);
	if (rc)
		return rc;

	hook = devm_kzalloc(&pdev->dev, sizeof(*hook), GFP_KERNEL);
	hook->fn = bridge_serr_hook;
	hook->priv = bank;
	bank->hook = hook;

	raw_spin_lock_irqsave(&bridge_lock, flags);
	list_add(&bank->node, &bridge_list);
	raw_spin_unlock_irqrestore(&bridge_lock, flags);

	register_serr_hook(hook);

	/*
	 * Flush out (and report) any early bridge errors.
	 */
	print_bank_info(NULL, bank);

	if (of_property_read_u32(pdev->dev.of_node, "timeout", &timeout) == 0) {
		if (timeout > 0 && timeout < 100000) {
			bank->setup_timeout(bank->vaddr, timeout, &pdev->dev);
			bank->timeout = timeout;
		}
		else
			dev_err(&pdev->dev, "error setting up timeout = %u\n",
				 timeout);
	}

	dev_info(&pdev->dev, "bridge probed OK\n");

	return 0;
}

static int tegra18_bridge_remove(struct platform_device *pdev)
{
	struct resource *res_base;
	struct bridge_mca_bank *bank;
	unsigned long flags;

	res_base = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	if (!res_base)
		return 0;

	raw_spin_lock_irqsave(&bridge_lock, flags);
	list_for_each_entry(bank, &bridge_list, node) {
		if (bank->bank == res_base->start) {
			unregister_serr_hook(bank->hook);
			list_del(&bank->node);
			break;
		}
	}
	raw_spin_unlock_irqrestore(&bridge_lock, flags);

	return 0;
}

static struct platform_driver platform_driver = {
	.probe		= tegra18_bridge_probe,
	.remove		= tegra18_bridge_remove,
	.driver = {
		.owner	= THIS_MODULE,
		.name	= "tegra18-bridge",
		.of_match_table = of_match_ptr(tegra18_bridge_match),
	},
};

module_platform_driver(platform_driver);

MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("Bridge Machine Check / SError handler");
